#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
#define NONE       -1
#define IN_RDIR     0
#define OUT_RDIR    1
#define APPEND_RDIR 2

char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];
char* rdirfilename = NULL;
int rdir = NONE; 

const char* getusername()
{
    return getenv("USER");
}
const char* Gethostname()
{
    return getenv("HOSTNAME");
}
void getpwd()
{
    getcwd(pwd,sizeof(pwd));
}
void check_rdir(char* cmd)
{
    char* pos = cmd;
    while(*pos!='\0')
    {
        if(*pos == '>')
        {
            if(*(pos + 1) == '>')
            {
                *pos++ = '\0';
                *pos++ = '\0';
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir=APPEND_RDIR;
                break;
            }
            else 
            {
                *pos = '\0';
                pos++;
                while(isspace(*pos)) pos++;
                rdirfilename = pos;
                rdir = OUT_RDIR;
                break;
            }
        }
        else if(*pos == '<')
        {
            *pos = '\0';  //ls -a -l < file.txt
            pos++;
            while(isspace(*pos)) pos++;
            rdirfilename = pos;
            rdir = IN_RDIR;
            break;
        }
        else 
        {
           // do nothing
        }
        pos++;
    }
}
void Interact(char* cline,int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),Gethostname(),pwd);
    char* s = fgets(cline,size,stdin);
    (void)s;
    assert(s);
    commandline[strlen(cline) - 1] = '\0';
    check_rdir(cline);
}
int splitstring(char cline[],char* _argv[])
{
    if(strcmp(cline,"") == 0) return 0;
    int i = 0;
    _argv[i++] = strtok(cline,DELIM);
    while(_argv[i++] = strtok(NULL,DELIM));
    return i - 1;
}

void NormalExcute(char* _argv[])
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return;
    }
    else if (id == 0)
    {
        int fd = 0;
        if(rdir == IN_RDIR)
        {
            fd = open(rdirfilename,O_RDONLY);
            dup2(fd,0);
        }
        else if(rdir == OUT_RDIR)
        {
            fd = open(rdirfilename,O_WRONLY|O_CREAT|O_TRUNC,0666);
            dup2(fd,1);
        }
        else if(rdir == APPEND_RDIR) 
        {
            fd = open(rdirfilename,O_WRONLY|O_CREAT|O_APPEND,0666);
            dup2(fd,1);
        }
        //子进程执行命令
        execvpe(_argv[0],_argv,environ);
        exit(EXIT_CODE);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        if(rid == id)
        {
           last_code = WEXITSTATUS(status);
        }
    }
}

int BuildCommand(char* _argv[],int _argc)
{
    if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
    {
        chdir(_argv[1]);
        getpwd();
        sprintf(getenv("PWD"),"%s",pwd);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"export") == 0)
    {
        strcpy(myenv,_argv[1]);
        putenv(myenv);
        return 1;
    }
    else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
    {
        if(strcmp(_argv[1],"$?") == 0)
        {
            printf("%d\n",last_code);
            last_code = 0;
        }
        else if(*_argv[1] == '$')
        {
            char* val = getenv(_argv[1] + 1);
            if(val) printf("%s\n",val);
        }
        else 
        {
            printf("%s\n",_argv[1]);
        }
        return 1;
    }


    if(_argc > 0 && strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}
int main()
{
    while(!quit)
    {
        rdirfilename = NULL;
        rdir = NONE;
        //2.交互问题，解决命令行
        Interact(commandline,sizeof(commandline));
        //3.子串分割问题，解析命令行
        int argc = splitstring(commandline,argv);
        if(argc == 0) continue;

      //  for(int i = 0; argv[i]; i++)
      //  {
      //      printf("[%d] : %s\n",i,argv[i]);
      //  }
        
        //4.指令的判断(内建命令和普通命令)
        int n = BuildCommand(argv,argc);
        // 4.0 分析输入的命令行字符串，获取有多少个|，命令打散多个子命令字符串
        // 4.1 malloc申请空间，pipe先申请多个管道
        // 4.2 循环创建多个子进程，每一个子进程的重定向情况。最开始，输出重定向，指定的第一个程序的1号文件要变成第一个管道文件的写端
        // 4.3 中间的需要进行输入输出重定向，0标准输入替换为上一个管道的读端，1标准输入替换为下一个管道的写端
        // 4.4 最后一个要进行输入重定向, 将0标准输入替换为上一个管道的读端
        // 4.5 分别让不同的子进程执行不同的命令 --- exec*  ---exec* 不会影响该进程曾经打开的文件，不会影响预先设置好的管道重定向
        

        //5.普通命令的执行
        if(!n) NormalExcute(argv);
    }
    return 0;
}
